/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.sun.org.apache.xml.internal.security.keys;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.crypto.SecretKey;

import com.sun.org.apache.xml.internal.security.encryption.EncryptedKey;
import com.sun.org.apache.xml.internal.security.encryption.XMLCipher;
import com.sun.org.apache.xml.internal.security.encryption.XMLEncryptionException;
import com.sun.org.apache.xml.internal.security.exceptions.XMLSecurityException;
import com.sun.org.apache.xml.internal.security.keys.content.DEREncodedKeyValue;
import com.sun.org.apache.xml.internal.security.keys.content.KeyInfoReference;
import com.sun.org.apache.xml.internal.security.keys.content.KeyName;
import com.sun.org.apache.xml.internal.security.keys.content.KeyValue;
import com.sun.org.apache.xml.internal.security.keys.content.MgmtData;
import com.sun.org.apache.xml.internal.security.keys.content.PGPData;
import com.sun.org.apache.xml.internal.security.keys.content.RetrievalMethod;
import com.sun.org.apache.xml.internal.security.keys.content.SPKIData;
import com.sun.org.apache.xml.internal.security.keys.content.X509Data;
import com.sun.org.apache.xml.internal.security.keys.content.keyvalues.DSAKeyValue;
import com.sun.org.apache.xml.internal.security.keys.content.keyvalues.RSAKeyValue;
import com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolver;
import com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolverException;
import com.sun.org.apache.xml.internal.security.keys.keyresolver.KeyResolverSpi;
import com.sun.org.apache.xml.internal.security.keys.storage.StorageResolver;
import com.sun.org.apache.xml.internal.security.transforms.Transforms;
import com.sun.org.apache.xml.internal.security.utils.Constants;
import com.sun.org.apache.xml.internal.security.utils.EncryptionConstants;
import com.sun.org.apache.xml.internal.security.utils.SignatureElementProxy;
import com.sun.org.apache.xml.internal.security.utils.XMLUtils;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * This class stand for KeyInfo Element that may contain keys, names,
 * certificates and other public key management information,
 * such as in-band key distribution or key agreement data.
 * <BR />
 * KeyInfo Element has two basic functions:
 * One is KeyResolve for getting the public key in signature validation processing.
 * the other one is toElement for getting the element in signature generation processing.
 * <BR />
 * The <CODE>lengthXXX()</CODE> methods provide access to the internal Key
 * objects:
 * <UL>
 * <LI>If the <CODE>KeyInfo</CODE> was constructed from an Element
 * (Signature verification), the <CODE>lengthXXX()</CODE> methods searches
 * for child elements of <CODE>ds:KeyInfo</CODE> for known types. </LI>
 * <LI>If the <CODE>KeyInfo</CODE> was constructed from scratch (during
 * Signature generation), the <CODE>lengthXXX()</CODE> methods return the number
 * of <CODE>XXXs</CODE> objects already passed to the KeyInfo</LI>
 * </UL>
 * <BR />
 * The <CODE>addXXX()</CODE> methods are used for adding Objects of the
 * appropriate type to the <CODE>KeyInfo</CODE>. This is used during signature
 * generation.
 * <BR />
 * The <CODE>itemXXX(int i)</CODE> methods return the i'th object of the
 * corresponding type.
 * <BR />
 * The <CODE>containsXXX()</CODE> methods return <I>whether</I> the KeyInfo
 * contains the corresponding type.
 */
public class KeyInfo extends SignatureElementProxy {

  /**
   * {@link org.apache.commons.logging} logging facility
   */
  private static java.util.logging.Logger log =
      java.util.logging.Logger.getLogger(KeyInfo.class.getName());

  // We need at least one StorageResolver otherwise
  // the KeyResolvers would not be called.
  // The default StorageResolver is null.

  private List<X509Data> x509Datas = null;
  private List<EncryptedKey> encryptedKeys = null;

  private static final List<StorageResolver> nullList;

  static {
    List<StorageResolver> list = new ArrayList<StorageResolver>(1);
    list.add(null);
    nullList = java.util.Collections.unmodifiableList(list);
  }

  /**
   * Field storageResolvers
   */
  private List<StorageResolver> storageResolvers = nullList;

  /**
   * Stores the individual (per-KeyInfo) {@link KeyResolverSpi}s
   */
  private List<KeyResolverSpi> internalKeyResolvers = new ArrayList<KeyResolverSpi>();

  private boolean secureValidation;

  /**
   * Constructor KeyInfo
   */
  public KeyInfo(Document doc) {
    super(doc);

    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Constructor KeyInfo
   */
  public KeyInfo(Element element, String baseURI) throws XMLSecurityException {
    super(element, baseURI);

    Attr attr = element.getAttributeNodeNS(null, "Id");
    if (attr != null) {
      element.setIdAttributeNode(attr, true);
    }
  }

  /**
   * Set whether secure processing is enabled or not. The default is false.
   */
  public void setSecureValidation(boolean secureValidation) {
    this.secureValidation = secureValidation;
  }

  /**
   * Sets the <code>Id</code> attribute
   *
   * @param Id ID
   */
  public void setId(String id) {
    if (id != null) {
      this.constructionElement.setAttributeNS(null, Constants._ATT_ID, id);
      this.constructionElement.setIdAttributeNS(null, Constants._ATT_ID, true);
    }
  }

  /**
   * Returns the <code>Id</code> attribute
   *
   * @return the <code>Id</code> attribute
   */
  public String getId() {
    return this.constructionElement.getAttributeNS(null, Constants._ATT_ID);
  }

  /**
   * Method addKeyName
   */
  public void addKeyName(String keynameString) {
    this.add(new KeyName(this.doc, keynameString));
  }

  /**
   * Method add
   */
  public void add(KeyName keyname) {
    this.constructionElement.appendChild(keyname.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addKeyValue
   */
  public void addKeyValue(PublicKey pk) {
    this.add(new KeyValue(this.doc, pk));
  }

  /**
   * Method addKeyValue
   */
  public void addKeyValue(Element unknownKeyValueElement) {
    this.add(new KeyValue(this.doc, unknownKeyValueElement));
  }

  /**
   * Method add
   */
  public void add(DSAKeyValue dsakeyvalue) {
    this.add(new KeyValue(this.doc, dsakeyvalue));
  }

  /**
   * Method add
   */
  public void add(RSAKeyValue rsakeyvalue) {
    this.add(new KeyValue(this.doc, rsakeyvalue));
  }

  /**
   * Method add
   */
  public void add(PublicKey pk) {
    this.add(new KeyValue(this.doc, pk));
  }

  /**
   * Method add
   */
  public void add(KeyValue keyvalue) {
    this.constructionElement.appendChild(keyvalue.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addMgmtData
   */
  public void addMgmtData(String mgmtdata) {
    this.add(new MgmtData(this.doc, mgmtdata));
  }

  /**
   * Method add
   */
  public void add(MgmtData mgmtdata) {
    this.constructionElement.appendChild(mgmtdata.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addPGPData
   */
  public void add(PGPData pgpdata) {
    this.constructionElement.appendChild(pgpdata.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addRetrievalMethod
   */
  public void addRetrievalMethod(String uri, Transforms transforms, String Type) {
    this.add(new RetrievalMethod(this.doc, uri, transforms, Type));
  }

  /**
   * Method add
   */
  public void add(RetrievalMethod retrievalmethod) {
    this.constructionElement.appendChild(retrievalmethod.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method add
   */
  public void add(SPKIData spkidata) {
    this.constructionElement.appendChild(spkidata.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addX509Data
   */
  public void add(X509Data x509data) {
    if (x509Datas == null) {
      x509Datas = new ArrayList<X509Data>();
    }
    x509Datas.add(x509data);
    this.constructionElement.appendChild(x509data.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addEncryptedKey
   */

  public void add(EncryptedKey encryptedKey) throws XMLEncryptionException {
    if (encryptedKeys == null) {
      encryptedKeys = new ArrayList<EncryptedKey>();
    }
    encryptedKeys.add(encryptedKey);
    XMLCipher cipher = XMLCipher.getInstance();
    this.constructionElement.appendChild(cipher.martial(encryptedKey));
  }

  /**
   * Method addDEREncodedKeyValue
   */
  public void addDEREncodedKeyValue(PublicKey pk) throws XMLSecurityException {
    this.add(new DEREncodedKeyValue(this.doc, pk));
  }

  /**
   * Method add
   */
  public void add(DEREncodedKeyValue derEncodedKeyValue) {
    this.constructionElement.appendChild(derEncodedKeyValue.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addKeyInfoReference
   */
  public void addKeyInfoReference(String URI) throws XMLSecurityException {
    this.add(new KeyInfoReference(this.doc, URI));
  }

  /**
   * Method add
   */
  public void add(KeyInfoReference keyInfoReference) {
    this.constructionElement.appendChild(keyInfoReference.getElement());
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method addUnknownElement
   */
  public void addUnknownElement(Element element) {
    this.constructionElement.appendChild(element);
    XMLUtils.addReturnToElement(this.constructionElement);
  }

  /**
   * Method lengthKeyName
   *
   * @return the number of the KeyName tags
   */
  public int lengthKeyName() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_KEYNAME);
  }

  /**
   * Method lengthKeyValue
   *
   * @return the number of the KeyValue tags
   */
  public int lengthKeyValue() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_KEYVALUE);
  }

  /**
   * Method lengthMgmtData
   *
   * @return the number of the MgmtData tags
   */
  public int lengthMgmtData() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_MGMTDATA);
  }

  /**
   * Method lengthPGPData
   *
   * @return the number of the PGPDat. tags
   */
  public int lengthPGPData() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_PGPDATA);
  }

  /**
   * Method lengthRetrievalMethod
   *
   * @return the number of the RetrievalMethod tags
   */
  public int lengthRetrievalMethod() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_RETRIEVALMETHOD);
  }

  /**
   * Method lengthSPKIData
   *
   * @return the number of the SPKIData tags
   */
  public int lengthSPKIData() {
    return this.length(Constants.SignatureSpecNS, Constants._TAG_SPKIDATA);
  }

  /**
   * Method lengthX509Data
   *
   * @return the number of the X509Data tags
   */
  public int lengthX509Data() {
    if (x509Datas != null) {
      return x509Datas.size();
    }
    return this.length(Constants.SignatureSpecNS, Constants._TAG_X509DATA);
  }

  /**
   * Method lengthDEREncodedKeyValue
   *
   * @return the number of the DEREncodedKeyValue tags
   */
  public int lengthDEREncodedKeyValue() {
    return this.length(Constants.SignatureSpec11NS, Constants._TAG_DERENCODEDKEYVALUE);
  }

  /**
   * Method lengthKeyInfoReference
   *
   * @return the number of the KeyInfoReference tags
   */
  public int lengthKeyInfoReference() {
    return this.length(Constants.SignatureSpec11NS, Constants._TAG_KEYINFOREFERENCE);
  }

  /**
   * Method lengthUnknownElement
   * NOTE possibly buggy.
   *
   * @return the number of the UnknownElement tags
   */
  public int lengthUnknownElement() {
    int res = 0;
    NodeList nl = this.constructionElement.getChildNodes();

    for (int i = 0; i < nl.getLength(); i++) {
      Node current = nl.item(i);

      /**
       * $todo$ using this method, we don't see unknown Elements
       *  from Signature NS; revisit
       */
      if ((current.getNodeType() == Node.ELEMENT_NODE)
          && current.getNamespaceURI().equals(Constants.SignatureSpecNS)) {
        res++;
      }
    }

    return res;
  }

  /**
   * Method itemKeyName
   *
   * @return the asked KeyName element, null if the index is too big
   */
  public KeyName itemKeyName(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_KEYNAME, i);

    if (e != null) {
      return new KeyName(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemKeyValue
   *
   * @return the asked KeyValue element, null if the index is too big
   */
  public KeyValue itemKeyValue(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_KEYVALUE, i);

    if (e != null) {
      return new KeyValue(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemMgmtData
   *
   * @return the asked MgmtData element, null if the index is too big
   */
  public MgmtData itemMgmtData(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_MGMTDATA, i);

    if (e != null) {
      return new MgmtData(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemPGPData
   *
   * @return the asked PGPData element, null if the index is too big
   */
  public PGPData itemPGPData(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_PGPDATA, i);

    if (e != null) {
      return new PGPData(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemRetrievalMethod
   *
   * @return the asked RetrievalMethod element, null if the index is too big
   */
  public RetrievalMethod itemRetrievalMethod(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_RETRIEVALMETHOD, i);

    if (e != null) {
      return new RetrievalMethod(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemSPKIData
   *
   * @return the asked SPKIData element, null if the index is too big
   */
  public SPKIData itemSPKIData(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_SPKIDATA, i);

    if (e != null) {
      return new SPKIData(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemX509Data
   *
   * @return the asked X509Data element, null if the index is too big
   */
  public X509Data itemX509Data(int i) throws XMLSecurityException {
    if (x509Datas != null) {
      return x509Datas.get(i);
    }
    Element e =
        XMLUtils.selectDsNode(
            this.constructionElement.getFirstChild(), Constants._TAG_X509DATA, i);

    if (e != null) {
      return new X509Data(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemEncryptedKey
   *
   * @return the asked EncryptedKey element, null if the index is too big
   */
  public EncryptedKey itemEncryptedKey(int i) throws XMLSecurityException {
    if (encryptedKeys != null) {
      return encryptedKeys.get(i);
    }
    Element e =
        XMLUtils.selectXencNode(
            this.constructionElement.getFirstChild(), EncryptionConstants._TAG_ENCRYPTEDKEY, i);

    if (e != null) {
      XMLCipher cipher = XMLCipher.getInstance();
      cipher.init(XMLCipher.UNWRAP_MODE, null);
      return cipher.loadEncryptedKey(e);
    }
    return null;
  }

  /**
   * Method itemDEREncodedKeyValue
   *
   * @return the asked DEREncodedKeyValue element, null if the index is too big
   */
  public DEREncodedKeyValue itemDEREncodedKeyValue(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDs11Node(
            this.constructionElement.getFirstChild(), Constants._TAG_DERENCODEDKEYVALUE, i);

    if (e != null) {
      return new DEREncodedKeyValue(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemKeyInfoReference
   *
   * @return the asked KeyInfoReference element, null if the index is too big
   */
  public KeyInfoReference itemKeyInfoReference(int i) throws XMLSecurityException {
    Element e =
        XMLUtils.selectDs11Node(
            this.constructionElement.getFirstChild(), Constants._TAG_KEYINFOREFERENCE, i);

    if (e != null) {
      return new KeyInfoReference(e, this.baseURI);
    }
    return null;
  }

  /**
   * Method itemUnknownElement
   *
   * @param i index
   * @return the element number of the unknown elements
   */
  public Element itemUnknownElement(int i) {
    NodeList nl = this.constructionElement.getChildNodes();
    int res = 0;

    for (int j = 0; j < nl.getLength(); j++) {
      Node current = nl.item(j);

      /**
       * $todo$ using this method, we don't see unknown Elements
       *  from Signature NS; revisit
       */
      if ((current.getNodeType() == Node.ELEMENT_NODE)
          && current.getNamespaceURI().equals(Constants.SignatureSpecNS)) {
        res++;

        if (res == i) {
          return (Element) current;
        }
      }
    }

    return null;
  }

  /**
   * Method isEmpty
   *
   * @return true if the element has no descendants.
   */
  public boolean isEmpty() {
    return this.constructionElement.getFirstChild() == null;
  }

  /**
   * Method containsKeyName
   *
   * @return If the KeyInfo contains a KeyName node
   */
  public boolean containsKeyName() {
    return this.lengthKeyName() > 0;
  }

  /**
   * Method containsKeyValue
   *
   * @return If the KeyInfo contains a KeyValue node
   */
  public boolean containsKeyValue() {
    return this.lengthKeyValue() > 0;
  }

  /**
   * Method containsMgmtData
   *
   * @return If the KeyInfo contains a MgmtData node
   */
  public boolean containsMgmtData() {
    return this.lengthMgmtData() > 0;
  }

  /**
   * Method containsPGPData
   *
   * @return If the KeyInfo contains a PGPData node
   */
  public boolean containsPGPData() {
    return this.lengthPGPData() > 0;
  }

  /**
   * Method containsRetrievalMethod
   *
   * @return If the KeyInfo contains a RetrievalMethod node
   */
  public boolean containsRetrievalMethod() {
    return this.lengthRetrievalMethod() > 0;
  }

  /**
   * Method containsSPKIData
   *
   * @return If the KeyInfo contains a SPKIData node
   */
  public boolean containsSPKIData() {
    return this.lengthSPKIData() > 0;
  }

  /**
   * Method containsUnknownElement
   *
   * @return If the KeyInfo contains a UnknownElement node
   */
  public boolean containsUnknownElement() {
    return this.lengthUnknownElement() > 0;
  }

  /**
   * Method containsX509Data
   *
   * @return If the KeyInfo contains a X509Data node
   */
  public boolean containsX509Data() {
    return this.lengthX509Data() > 0;
  }

  /**
   * Method containsDEREncodedKeyValue
   *
   * @return If the KeyInfo contains a DEREncodedKeyValue node
   */
  public boolean containsDEREncodedKeyValue() {
    return this.lengthDEREncodedKeyValue() > 0;
  }

  /**
   * Method containsKeyInfoReference
   *
   * @return If the KeyInfo contains a KeyInfoReference node
   */
  public boolean containsKeyInfoReference() {
    return this.lengthKeyInfoReference() > 0;
  }

  /**
   * This method returns the public key.
   *
   * @return If the KeyInfo contains a PublicKey node
   */
  public PublicKey getPublicKey() throws KeyResolverException {
    PublicKey pk = this.getPublicKeyFromInternalResolvers();

    if (pk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a key using the per-KeyInfo key resolvers");
      }

      return pk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a key using the per-KeyInfo key resolvers");
    }

    pk = this.getPublicKeyFromStaticResolvers();

    if (pk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a key using the system-wide key resolvers");
      }

      return pk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a key using the system-wide key resolvers");
    }

    return null;
  }

  /**
   * Searches the library wide KeyResolvers for public keys
   *
   * @return The public key contained in this Node.
   */
  PublicKey getPublicKeyFromStaticResolvers() throws KeyResolverException {
    Iterator<KeyResolverSpi> it = KeyResolver.iterator();
    while (it.hasNext()) {
      KeyResolverSpi keyResolver = it.next();
      keyResolver.setSecureValidation(secureValidation);
      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          for (StorageResolver storage : storageResolvers) {
            PublicKey pk =
                keyResolver.engineLookupAndResolvePublicKey(
                    (Element) currentChild, uri, storage
                );

            if (pk != null) {
              return pk;
            }
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }
    return null;
  }

  /**
   * Searches the per-KeyInfo KeyResolvers for public keys
   *
   * @return The public key contained in this Node.
   */
  PublicKey getPublicKeyFromInternalResolvers() throws KeyResolverException {
    for (KeyResolverSpi keyResolver : internalKeyResolvers) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE, "Try " + keyResolver.getClass().getName());
      }
      keyResolver.setSecureValidation(secureValidation);
      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          for (StorageResolver storage : storageResolvers) {
            PublicKey pk =
                keyResolver.engineLookupAndResolvePublicKey(
                    (Element) currentChild, uri, storage
                );

            if (pk != null) {
              return pk;
            }
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }

    return null;
  }

  /**
   * Method getX509Certificate
   *
   * @return The certificate contained in this KeyInfo
   */
  public X509Certificate getX509Certificate() throws KeyResolverException {
    // First search using the individual resolvers from the user
    X509Certificate cert = this.getX509CertificateFromInternalResolvers();

    if (cert != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a X509Certificate using the per-KeyInfo key resolvers");
      }

      return cert;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a X509Certificate using the per-KeyInfo key resolvers");
    }

    // Then use the system-wide Resolvers
    cert = this.getX509CertificateFromStaticResolvers();

    if (cert != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a X509Certificate using the system-wide key resolvers");
      }

      return cert;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a X509Certificate using the system-wide key resolvers");
    }

    return null;
  }

  /**
   * This method uses each System-wide {@link KeyResolver} to search the
   * child elements. Each combination of {@link KeyResolver} and child element
   * is checked against all {@link StorageResolver}s.
   *
   * @return The certificate contained in this KeyInfo
   */
  X509Certificate getX509CertificateFromStaticResolvers()
      throws KeyResolverException {
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "Start getX509CertificateFromStaticResolvers() with " + KeyResolver.length()
              + " resolvers"
      );
    }
    String uri = this.getBaseURI();
    Iterator<KeyResolverSpi> it = KeyResolver.iterator();
    while (it.hasNext()) {
      KeyResolverSpi keyResolver = it.next();
      keyResolver.setSecureValidation(secureValidation);
      X509Certificate cert = applyCurrentResolver(uri, keyResolver);
      if (cert != null) {
        return cert;
      }
    }
    return null;
  }

  private X509Certificate applyCurrentResolver(
      String uri, KeyResolverSpi keyResolver
  ) throws KeyResolverException {
    Node currentChild = this.constructionElement.getFirstChild();
    while (currentChild != null) {
      if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
        for (StorageResolver storage : storageResolvers) {
          X509Certificate cert =
              keyResolver.engineLookupResolveX509Certificate(
                  (Element) currentChild, uri, storage
              );

          if (cert != null) {
            return cert;
          }
        }
      }
      currentChild = currentChild.getNextSibling();
    }
    return null;
  }

  /**
   * Method getX509CertificateFromInternalResolvers
   *
   * @return The certificate contained in this KeyInfo
   */
  X509Certificate getX509CertificateFromInternalResolvers()
      throws KeyResolverException {
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "Start getX509CertificateFromInternalResolvers() with "
              + this.lengthInternalKeyResolver() + " resolvers"
      );
    }
    String uri = this.getBaseURI();
    for (KeyResolverSpi keyResolver : internalKeyResolvers) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE, "Try " + keyResolver.getClass().getName());
      }
      keyResolver.setSecureValidation(secureValidation);
      X509Certificate cert = applyCurrentResolver(uri, keyResolver);
      if (cert != null) {
        return cert;
      }
    }

    return null;
  }

  /**
   * This method returns a secret (symmetric) key. This is for XML Encryption.
   *
   * @return the secret key contained in this KeyInfo
   */
  public SecretKey getSecretKey() throws KeyResolverException {
    SecretKey sk = this.getSecretKeyFromInternalResolvers();

    if (sk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a secret key using the per-KeyInfo key resolvers");
      }

      return sk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a secret key using the per-KeyInfo key resolvers");
    }

    sk = this.getSecretKeyFromStaticResolvers();

    if (sk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a secret key using the system-wide key resolvers");
      }

      return sk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a secret key using the system-wide key resolvers");
    }

    return null;
  }

  /**
   * Searches the library wide KeyResolvers for Secret keys
   *
   * @return the secret key contained in this KeyInfo
   */
  SecretKey getSecretKeyFromStaticResolvers() throws KeyResolverException {
    Iterator<KeyResolverSpi> it = KeyResolver.iterator();
    while (it.hasNext()) {
      KeyResolverSpi keyResolver = it.next();
      keyResolver.setSecureValidation(secureValidation);

      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          for (StorageResolver storage : storageResolvers) {
            SecretKey sk =
                keyResolver.engineLookupAndResolveSecretKey(
                    (Element) currentChild, uri, storage
                );

            if (sk != null) {
              return sk;
            }
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }
    return null;
  }

  /**
   * Searches the per-KeyInfo KeyResolvers for secret keys
   *
   * @return the secret key contained in this KeyInfo
   */

  SecretKey getSecretKeyFromInternalResolvers() throws KeyResolverException {
    for (KeyResolverSpi keyResolver : internalKeyResolvers) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE, "Try " + keyResolver.getClass().getName());
      }
      keyResolver.setSecureValidation(secureValidation);
      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          for (StorageResolver storage : storageResolvers) {
            SecretKey sk =
                keyResolver.engineLookupAndResolveSecretKey(
                    (Element) currentChild, uri, storage
                );

            if (sk != null) {
              return sk;
            }
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }

    return null;
  }

  /**
   * This method returns a private key. This is for Key Transport in XML Encryption.
   *
   * @return the private key contained in this KeyInfo
   */
  public PrivateKey getPrivateKey() throws KeyResolverException {
    PrivateKey pk = this.getPrivateKeyFromInternalResolvers();

    if (pk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a private key using the per-KeyInfo key resolvers");
      }
      return pk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a secret key using the per-KeyInfo key resolvers");
    }

    pk = this.getPrivateKeyFromStaticResolvers();
    if (pk != null) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE,
            "I could find a private key using the system-wide key resolvers");
      }
      return pk;
    }
    if (log.isLoggable(java.util.logging.Level.FINE)) {
      log.log(java.util.logging.Level.FINE,
          "I couldn't find a private key using the system-wide key resolvers");
    }

    return null;
  }

  /**
   * Searches the library wide KeyResolvers for Private keys
   *
   * @return the private key contained in this KeyInfo
   */
  PrivateKey getPrivateKeyFromStaticResolvers() throws KeyResolverException {
    Iterator<KeyResolverSpi> it = KeyResolver.iterator();
    while (it.hasNext()) {
      KeyResolverSpi keyResolver = it.next();
      keyResolver.setSecureValidation(secureValidation);

      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          // not using StorageResolvers at the moment
          // since they cannot return private keys
          PrivateKey pk =
              keyResolver.engineLookupAndResolvePrivateKey(
                  (Element) currentChild, uri, null
              );

          if (pk != null) {
            return pk;
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }
    return null;
  }

  /**
   * Searches the per-KeyInfo KeyResolvers for private keys
   *
   * @return the private key contained in this KeyInfo
   */
  PrivateKey getPrivateKeyFromInternalResolvers() throws KeyResolverException {
    for (KeyResolverSpi keyResolver : internalKeyResolvers) {
      if (log.isLoggable(java.util.logging.Level.FINE)) {
        log.log(java.util.logging.Level.FINE, "Try " + keyResolver.getClass().getName());
      }
      keyResolver.setSecureValidation(secureValidation);
      Node currentChild = this.constructionElement.getFirstChild();
      String uri = this.getBaseURI();
      while (currentChild != null) {
        if (currentChild.getNodeType() == Node.ELEMENT_NODE) {
          // not using StorageResolvers at the moment
          // since they cannot return private keys
          PrivateKey pk =
              keyResolver.engineLookupAndResolvePrivateKey(
                  (Element) currentChild, uri, null
              );

          if (pk != null) {
            return pk;
          }
        }
        currentChild = currentChild.getNextSibling();
      }
    }

    return null;
  }

  /**
   * This method is used to add a custom {@link KeyResolverSpi} to a KeyInfo
   * object.
   */
  public void registerInternalKeyResolver(KeyResolverSpi realKeyResolver) {
    this.internalKeyResolvers.add(realKeyResolver);
  }

  /**
   * Method lengthInternalKeyResolver
   *
   * @return the length of the key
   */
  int lengthInternalKeyResolver() {
    return this.internalKeyResolvers.size();
  }

  /**
   * Method itemInternalKeyResolver
   *
   * @param i the index
   * @return the KeyResolverSpi for the index.
   */
  KeyResolverSpi itemInternalKeyResolver(int i) {
    return this.internalKeyResolvers.get(i);
  }

  /**
   * Method addStorageResolver
   */
  public void addStorageResolver(StorageResolver storageResolver) {
    if (storageResolvers == nullList) {
      // Replace the default null StorageResolver
      storageResolvers = new ArrayList<StorageResolver>();
    }
    this.storageResolvers.add(storageResolver);
  }


  /**
   * @inheritDoc
   */
  public String getBaseLocalName() {
    return Constants._TAG_KEYINFO;
  }
}
